#include "main.h"
#include "webglk/glk.h"
#include "zcode.h"
#include <stdlib.h>
#include <stdio.h>
#include <time.h>

#include "zcode_ops.h"

static void init_header();
static void seed_random(int);
static void operand(uint8_t);
static void operands(uint8_t);
static void store(uint16_t);
static void branch(uint16_t);
static void call(uint16_t,int8_t,uint16_t *,uint8_t);
static void ret(uint16_t);
static uint16_t first_prop(uint16_t);
static uint16_t next_prop(uint16_t);
static uint16_t zscii2ascii(uint8_t);
static uint8_t ascii2zscii(uint8_t);
static void decode(uint8_t,uint32_t);
static void unlink_object(uint16_t);
static void load_string(uint16_t,uint8_t);
static void encode_text(uint8_t);
static void tokenize_line(uint16_t,uint16_t,uint16_t,uint8_t);
static void put_font_char(uint16_t);

static uint8_t argc;
static uint16_t args[8];

static uint8_t decoded[10];
static uint16_t encoded[3];

void z_init()
{
	g.version=rom[0];
	if (g.version<1 || g.version>8 || g.version==6) fatal("Invalid z-code version: %d",g.version);

	g.stackSize=0x4000;

	g.iosys=2;

	g.extensions=read16(54);
	g.numExtensions=g.extensions?read16(g.extensions):0;
	if (g.numExtensions<3) g.unicode=0;
	else g.unicode=read16(g.extensions+2*3);

	g.memory=0;
	g.screen=1;

	stack=malloc(g.stackSize);
	g.wins[0]=glk_window_open(0,0,0,wintype_TextBuffer,0);
	glk_set_window(g.wins[0]);

	seed_random(0);
	init_header();
	SP=FP=0;
	if (g.version==6)
		call(read16(6),0,0,0);
	else
		PC=read16(6);

}
void z_select(event_t *event)
{
	int i,ofs;
	if (event->type==evtype_LineInput)
	{
		ofs=g.inputbuffer+(g.version<5?1:2);
		for (i=0;i<event->val1;i++)
			rom[ofs+i]=ascii2zscii(rom[ofs+i]);
		if (g.tokens) tokenize_line(g.inputbuffer,g.tokens,0,0);
		if (g.version>4) store(zscii2ascii(13));
	}
	else if (event->type==evtype_CharInput)
	{
		store(ascii2zscii(event->val1));
	}
	else if (event->type==evtype_Timer)
	{
		call(g.timerfunc,0,0,2);
		if (pop())
		{
			if (g.version>4 || g.inputbuffer==0) store(zscii2ascii(0));
		}
		else
		{
			glk_request_timer_events(g.timer);
			if (g.inputbuffer==0)
				glk_request_char_event(g.wins[g.curwin]);
			else
				glk_request_line_event(g.wins[g.curwin],NULL,255,0);
			done++;
		}
	}
}
void z_run()
{
	uint8_t op;
	while (!done)
	{
		op=rom[PC++];
		argc=0;
		if (op<0x80)
		{
			operand((op&0x40)?2:1);
			operand((op&0x20)?2:1);
			var_opcodes[op&0x1f]();
		}
		else if (op<0xb0)
		{
			operand(op>>4);
			op1_opcodes[op&0x0f]();
		}
		else if (op<0xc0)
			op0_opcodes[op-0xb0]();
		else if (op==0xec || op==0xfa)
		{
			uint8_t t1=rom[PC++];
			uint8_t t2=rom[PC++];
			operands(t1);
			operands(t2);
			var_opcodes[op-0xc0]();
		}
		else
		{
			operands(rom[PC++]);
			var_opcodes[op-0xc0]();
		}
	}
	done--;
}
static void __extended__()
{
	uint8_t opcode,types;
	opcode=rom[PC++];
	types=rom[PC++];
	operands(types);
	ext_opcodes[opcode&0x1f]();
}



static void __illegal__(){fatal("Illegal opcode: %x",PC);}
static void z_add()
{
	store((int16_t)args[0]+(int16_t)args[1]);
}
static void z_and()
{
	store(args[0]&args[1]);
}
static void z_art_shift()
{
	if ((int16_t)args[1]>0)
		store(args[0]<<args[1]);
	else
		store((int16_t)args[0]>>-(int16_t)args[1]);
}
static void z_buffer_mode()
{
}
static void z_buffer_screen(){fatal("buffer_screen");}
static void z_call_n()
{
	if (args[0]) call(args[0],argc-1,args+1,1);
}
static void z_call_s()
{
	if (args[0]) call(args[0],argc-1,args+1,0);
	else store(0);
}
static void z_catch()
{
	store(FP);
}
static void z_check_arg_count()
{
	if (FP==0)
		branch(args[0]==0);
	else
		branch(args[0]<=(peek(FP)&0xff));
}
static void z_check_unicode()
{
	uint16_t c=args[0];
	if (c>=' ' && c<='~') store(3);
	else if (c==0xa0) store(1);
	else if (c>=0xa1 && c<=0xff) store(3);
	else store(0);
}
static void z_clear_attr()
{
	uint16_t addr=object_address(args[0])+args[1]/8;
	rom[addr]&=~(0x80>>(args[1]&7));
}
static void z_copy_table()
{
	int16_t size=args[2],i;
	if (args[1]==0) for (i=0;i<size;i++) rom[args[0]+i]=0;
	else if (size<0 || args[0]>args[1])
		for (i=0;i<(size<0?-size:size);i++)
			rom[args[1]+i]=rom[args[0]+i];
	else for (i=size-1;i>=0;i--)
		rom[args[1]+i]=rom[args[0]+i];
}
static void z_dec()
{
	uint16_t v=args[0];
	if (v==0) push(pop()-1);
	else if (v<16) poke(FP+v*4,peek(FP+v*4)-1);
	else {
		uint16_t addr=read16(12)+2*(v-16);
		write16(addr,read16(addr)-1);
	}
}
static void z_dec_chk()
{
	uint16_t value;
	uint16_t v=args[0];
	if (v==0) push(value=pop()-1);
	else if (v<16) poke(FP+v*4,value=peek(FP+v*4)-1);
	else
	{
		uint16_t addr=read16(12)+2*(v-16);
		value=read16(addr)-1;
		write16(addr,value);
	}
	branch((int16_t)value<(int16_t)args[1]);
}
static void z_div()
{
	store((int16_t)args[0]/(int16_t)args[1]);
}
static void z_draw_picture(){fatal("draw_picture");}
static void z_encode_text()
{
	int i;
	load_string(args[0]+args[2],args[1]);
	encode_text(0x05);
	for (i=0;i<3;i++)
		write16(args[3]+2*i,encoded[i]);
}
static void z_erase_line(){fatal("erase_line");}
static void z_erase_picture(){fatal("erase_picture");}
static void z_erase_window()
{
	int i;
	int16_t w=args[0];
	if (w==-1)
	{
		glk_window_close(glk_window_get_root(),NULL);
		for (i=0;i<8;i++) g.wins[i]=0;
		g.wins[0]=glk_window_open(0,0,0,wintype_TextBuffer,0);
		if (!g.memory) glk_set_window(g.wins[0]);
		g.cursorx=g.cursory=0;
	}
	else if (w==-2)
	{
		g.cursorx=g.cursory=0;
		for (i=0;i<8;i++)
			if (g.wins[i])
				glk_window_clear(g.wins[i]);
	}
	else if (g.wins[w])
	{
		if (w==1) g.cursorx=g.cursory=0;
		glk_window_clear(g.wins[w]);
	}
	// some games are stupid and try to clear the statusbar after they 
	// clear -1, at which point there isn't a statusbar.
}
static void z_get_child()
{
	uint16_t addr=object_address(args[0]);
	if (g.version<4)
	{
		uint8_t child=rom[addr+6];
		store(child);
		branch(child);
	}
	else
	{
		uint16_t child=read16(addr+10);
		store(child);
		branch(child);
	}
}
static void z_get_cursor()
{
	// cursor is always at home
	write16(args[0],0);
	write16(args[0]+2,0);
}
static void z_get_next_prop()
{
	uint16_t addr;
	uint8_t value,mask=(g.version<4)?0x1f:0x3f;
	addr=first_prop(args[0]);
	if (args[1])
	{
		do {
			value=rom[addr];
			addr=next_prop(addr);
		} while ((value&mask)>args[1]);
	}
	store(rom[addr]&mask);
}
static void z_get_parent()
{
	uint16_t addr=object_address(args[0]);
	if (g.version<4)
		store(rom[addr+4]);
	else
		store(read16(addr+6));
}
static void z_get_prop()
{
	uint16_t addr;
	uint8_t mask,value;

	mask=(g.version<4)?0x1f:0x3f;
	addr=first_prop(args[0]);
	while (1)
	{
		value=rom[addr];
		if ((value&mask)<=args[1]) break;
		addr=next_prop(addr);
	}
	if ((value&mask)==args[1])
	{
		addr++;
		if (value&(g.version<4?0xe0:0xc0))
			store(read16(addr));
		else
			store(rom[addr]);
	}
	else
		store(read16(read16(10)+2*(args[1]-1)));
}
static void z_get_prop_addr()
{
	uint8_t value,mask=(g.version<4)?0x1f:0x3f;
	uint16_t addr=first_prop(args[0]);

	while (1)
	{
		value=rom[addr];
		if ((value&mask)<=args[1]) break;
		addr=next_prop(addr);
	}
	if ((value&mask)==args[1])
	{
		if (g.version>3 && (value&0x80)) addr++;
		store(addr+1);
	} else store(0);
}
static void z_get_prop_len()
{
	uint8_t value;
	uint16_t addr=args[0]-1;
	if (args[0]==0) { store(0); return; }

	value=rom[addr];
	if (g.version<4) value=(value>>5)+1;
	else if (value&0x80) value&=0x3f;
	else value=(value>>6)+1;
	store(value);
}
static void z_get_sibling()
{
	uint16_t addr=object_address(args[0]);
	if (g.version<4)
	{
		uint8_t sib=rom[addr+5];
		store(sib);
		branch(sib);
	}
	else
	{
		uint16_t sib=read16(addr+8);
		store(sib);
		branch(sib);
	}
}
static void z_get_wind_prop()
{
	switch (args[1])
	{
		default: fatal("get_wind_prop: %d",args[1]);
	}
}
static void z_inc()
{
	uint16_t v=args[0];
	if (v==0) push(pop()+1);
	else if (v<16) poke(FP+v*4,peek(FP+v*4)+1);
	else {
		uint16_t addr=read16(12)+2*(v-16);
		write16(addr,read16(addr)+1);
	}
}
static void z_inc_chk()
{
	uint16_t v=args[0];
	uint16_t val;

	if (v==0) push(val=pop()+1);
	else if (v<16) poke(FP+v*4,val=peek(FP+v*4)+1);
	else {
		uint16_t addr=read16(12)+2*(v-16);
		val=read16(addr)+1;
		write16(addr,val);
	}
	branch((int16_t)val>(int16_t)args[1]);
}
static void z_input_stream()
{
}
static void z_insert_obj()
{
	uint16_t a=args[0],b=args[1];
	uint16_t aa,bb;
	aa=object_address(a);
	bb=object_address(b);
	unlink_object(a);
	if (g.version<4)
	{
		rom[aa+4]=b;
		rom[aa+5]=rom[bb+6];
		rom[bb+6]=a;
	}
	else
	{
		write16(aa+6,b);
		write16(aa+8,read16(bb+10));
		write16(bb+10,a);
	}
}
static void z_jump()
{
	PC+=(int16_t)args[0]-2;
}
static void z_je()
{
	uint16_t i,n=0;
	for (i=0;i<argc-1;i++)
		n+=args[0]==args[i+1];
	branch(n>0);
}
static void z_jg()
{
	branch((int16_t)args[0]>(int16_t)args[1]);
}
static void z_jin()
{
	uint16_t addr=object_address(args[0]);
	if (g.version<4)
		branch(rom[addr+4]==args[1]);
	else
		branch(read16(addr+6)==args[1]);
}
static void z_jl()
{
	branch((int16_t)args[0]<(int16_t)args[1]);
}
static void z_jz()
{
	branch(!args[0]);
}
static void z_load()
{
	uint16_t v=args[0];
	if (v==0) store(peek(SP-4));
	else if (v<16) store(peek(FP+v*4));
	else store(read16(read16(12)+2*(v-16)));
}
static void z_loadb()
{
	store(rom[args[0]+args[1]]);
}
static void z_loadw()
{
	store(read16(args[0]+2*args[1]));
}
static void z_log_shift()
{
	if ((int16_t)args[1]>0)
		store(args[0]<<args[1]);
	else
		store(args[0]>>-(int16_t)args[1]);
}
static void z_make_menu(){fatal("make_menu");}
static void z_mod()
{
	store((int16_t)args[0]%(int16_t)args[1]);
}
static void z_mouse_window(){fatal("mouse_window");}
static void z_move_window()
{
	// not a real window, it's a write mask, and screw v6 roms.
}
static void z_mul()
{
	store((int16_t)args[0]*(int16_t)args[1]);
}
static void z_new_line()
{
	put_font_char('\n');
}
static void z_nop(){}
static void z_not()
{
	store(~args[0]);
}
static void z_or()
{
	store(args[0]|args[1]);
}
static void z_output_stream()
{
	stream_result_t result;
	switch ((int16_t)args[0])
	{
		case 1:
			if (!g.memory)
				glk_set_window(g.wins[g.curwin]);
			g.screen++;
			break;
		case -1:
			g.screen--;
			if (g.screen<1)
				glk_stream_set_current(0);
			break;
		case 3:
			g.mems[g.memory]=glk_stream_open_memory((char *)(rom+args[1]+2),0xffff,filemode_Write,0);
			g.memlens[g.memory]=args[1];
			glk_stream_set_current(g.mems[g.memory]);
			g.memory++;
			break;
		case -3:
			g.memory--;
			glk_stream_close(g.mems[g.memory],&result);
			write16(g.memlens[g.memory],result.writecount);
			if (g.memory) glk_stream_set_current(g.mems[g.memory-1]);
			else glk_set_window(g.wins[g.curwin]);
			break;
	}
}
static void z_picture_data()
{
	fatal("picture_data");
}
static void z_picture_table(){fatal("picture_table");}
static void z_piracy()
{
	branch(1);
}
static void z_pop()
{
	pop();
}
static void z_pop_stack(){fatal("pop_stack");}
static void z_print()
{
	decode(3,0);
}
static void z_print_addr()
{
	decode(0,args[0]);
}
static void z_print_char()
{
	put_font_char(zscii2ascii(args[0]));
}
static void z_print_form(){fatal("print_form");}
static void z_print_num()
{
	printNum((int16_t)args[0]);
}
static void z_print_obj()
{
	uint16_t addr=read16(object_address(args[0])+(g.version<4?7:12));
	if (rom[addr++]) decode(0,addr);
}
static void z_print_paddr()
{
	decode(2,args[0]);
}
static void z_print_ret()
{
	decode(3,0);
	put_font_char('\n');
	ret(1);
}
static void z_print_table()
{
	uint8_t c;
	int sx,x,y;
	uint16_t addr=args[0];
	if (argc<3) args[2]=1;
	if (argc<4) args[3]=0;
	glk_stream_buffer_flush();
	sx=g.cursorx;
	for (y=0;y<args[2];y++)
	{
		if (y) glk_window_move_cursor(g.wins[g.curwin],sx,++g.cursory);
		for (x=0;x<args[1];x++)
		{
			c=rom[addr++];
			if (c=='\n' || c=='\r') c=' ';
			put_font_char(c);
		}
		addr+=args[3];
	}
}
static void z_print_unicode()
{
	put_font_char(args[0]);
}
static void z_pull()
{
	uint16_t v=args[0];
	uint16_t val=pop();
	if (v==0) poke(SP-4,val);
	else if (v<16) poke(FP+v*4,val);
	else write16(read16(12)+2*(v-16),val);
}
static void z_push()
{
	push(args[0]);
}
static void z_push_stack(){fatal("push_stack");}
static void z_put_prop()
{
	uint16_t addr;
	uint8_t mask,value;

	mask=g.version<4?0x1f:0x3f;
	addr=first_prop(args[0]);

	while (1)
	{
		value=rom[addr];
		if ((value&mask)<=args[1]) break;
		addr=next_prop(addr);
	}
	addr++;
	if (value&(g.version<4?0xe0:0xc0))
		write16(addr,args[2]);
	else
		rom[addr]=args[2]&0xff;
}
static void z_put_wind_prop(){fatal("put_wind_prop");}
static void z_quit()
{
	done+=2;
}
static void z_random()
{
	if ((int16_t)args[0]<=0)
	{
		seed_random(-(int16_t)args[0]);
		store(0);
	}
	else
	{
		uint16_t r;
		if (g.randi)
		{
			r=g.randc++;
			if (g.randc==g.randi) g.randc=0;
		}
		else
		{
			g.randa=0x015a4e35L*g.randa+1;
			r=(g.randa>>16)&0x7fff;
		}
		store((r%args[0])+1);
	}
}
static void z_read()
{
	uint16_t text;
	uint8_t i,prefill=0;
	char *buf;

	g.inputbuffer=text=args[0];
	g.tokens=args[1];
	if (argc>2 && args[2])
	{
		glk_request_timer_events(args[2]*100);
		g.timer=args[2]*100;
		g.timerfunc=args[3];
	}
	if (g.version>4) prefill=rom[++text];
	if (g.version<4) z_show_status();
	buf=malloc(prefill);
	for (i=0;i<prefill;i++) buf[i]=zscii2ascii(rom[++text]);
	glk_request_line_event(g.wins[g.curwin],buf,255,prefill);
	free(buf);
	done++;
}
static void z_read_char()
{
	g.inputbuffer=0;
	if (argc>1 && args[1])
	{
		glk_request_timer_events(args[1]*100);
		g.timer=args[1]*100;
		g.timerfunc=args[2];
	}
	glk_request_char_event(g.wins[g.curwin]);
	done++;
}
static void z_read_mouse(){fatal("read_mouse");}
static void z_remove_obj()
{
	unlink_object(args[0]);
}
static void z_ret()
{
	ret(args[0]);
}
static void z_ret_popped()
{
	ret(pop());
}
static void z_restart()
{
	g.cursorx=g.cursory=0;
	glk_set_window(g.wins[0]);
	seed_random(0);
	loadROM(0);
	init_header();
	SP=FP=0;
	if (g.version==6)
		call(read16(6),0,0,0);
	else
		PC=read16(6);
}
static void z_restore()
{
	uint32_t a,b;
	uint8_t r;
	if (argc) r=load(args[0],args[1],&a,&b);
	else r=load(0,read16(14),&a,&b);
	if (g.version<4) branch(r);
	else store(r);
}
static void z_restore_undo()
{
	store(-1);
}
static void z_rfalse()
{
	ret(0);
}
static void z_rtrue()
{
	ret(1);
}
static void z_save()
{
	uint8_t r;
	if (argc) r=save(args[0],args[1],0,0);
	else r=save(0,read16(14),0,0);
	if (g.version<4) branch(r);
	else store(r);
}
static void z_save_undo()
{
	store(-1);
}
static void z_scan_table()
{
	uint16_t i,addr=args[1];
	if (argc<4) args[3]=0x82;
	for (i=0;i<args[2];i++)
	{
		if (args[3]&0x80)
		{
			if (read16(addr)==args[0]) break;
		}
		else if (rom[addr]==args[0]) break;
		addr+=args[3]&0x7f;
	}
	if (i==args[2]) addr=0;
	store(addr);
	branch(addr);
}
static void z_scroll_window(){fatal("scroll_window");}
static void z_set_attr()
{
	uint16_t addr=object_address(args[0])+args[1]/8;
	rom[addr]|=0x80>>(args[1]&7);
}
uint32_t colors[]={
	0,0,
	0x000000,
	0xe80000,
	0x00d000,
	0xe8e800,
	0x0068b0,
	0xff00ff,
	0x00e8e8,
	0xffffff,
	0xb0b0b0,
	0x888888,
	0x585858
};
static void z_set_colour()
{
	glui32 wt,i;
	wt=glk_window_get_type(g.wins[g.curwin]);

	if (wt==3) // we're going to exclude a few color changes here
	{
		args[1]=1;   //first, no changing backgrond color
		if (args[0]==9) // second, white on black is the default
			args[0]=2; // so we'll make it black on white
	}

	if (args[0])
	{
		if (args[0]==1) args[0]=2;
		for (i=0;i<9;i++)
		{
			glk_stylehint_set(wt,i,stylehint_TextColor,colors[args[0]]);
			if (i==2) i=7;
		}
	}
	if (args[1])
	{
		if (args[1]==1) args[1]=9;
		for (i=0;i<9;i++)
		{
			glk_stylehint_set(wt,i,stylehint_BackColor,colors[args[1]]);
			if (i==2) i=7;
		}
	}
}
static void z_set_cursor()
{
	uint16_t w=argc<3?g.curwin:args[2];
	if (g.wins[w])
	{
		g.cursorx=args[1]-1;
		g.cursory=args[0]-1;
		glk_window_move_cursor(g.wins[w],args[1]-1,args[0]-1);
	}
	// some games try to move the cursor of a closed statusbar
}
static uint8_t curFont=1;
static void z_set_font()
{
	if (args[0]==2) store(0);
	else
	{
		if (args[0]==4 || args[0]==3) glk_set_style(8);
		else glk_set_style(0);
		store(curFont);
		curFont=args[0];
	}
}
static void z_set_margins(){fatal("set_margins");}
static void z_set_text_style()
{
	uint8_t s=0;
	if (args[0]==2 || args[0]==4) s=1;
	if (args[0]==8) s=2;
	if (args[0]==1)
		glk_stylehint_set(glk_window_get_type(g.wins[g.curwin]),0,stylehint_ReverseColor,1);
	else
		glk_stylehint_set(glk_window_get_type(g.wins[g.curwin]),0,stylehint_ReverseColor,0);
	glk_set_style(s);
}
static void z_set_true_colour(){fatal("set_true_colour");}
static void z_set_window()
{
	g.cursorx=g.cursory=0;
	g.curwin=args[0];
	if (!g.memory) glk_set_window(g.wins[g.curwin]);
	if (args[0]==1) glk_window_move_cursor(g.wins[g.curwin],0,0);
}
static void z_show_status()
{
	uint16_t obj,score,moves,globals,addr;

	if (g.version>3) return;
	if (g.memory) return;

	globals=read16(12);
	obj=read16(globals);
	score=read16(globals+2);
	moves=read16(globals+4);

	glk_stylehint_set(wintype_TextGrid,0,stylehint_ReverseColor,1);
	glk_window_clear(g.wins[7]);
	glk_set_window(g.wins[7]);
	put_font_char(' ');

	addr=read16(object_address(obj)+(g.version<4?7:12));
	if (rom[addr++]) decode(0,addr);
	if (rom[1]&0x02)
	{
		uint8_t hours=((score+11)%12)+1;
		glk_window_move_cursor(g.wins[7],(screenwidth/font4width)-20,0);
		glk_put_string("Time: ");
		if (hours<10) put_font_char(' ');
		printNum(hours);
		put_font_char(':');
		if (moves<10) put_font_char('0');
		printNum(moves);
		put_font_char(' ');
		put_font_char((score>=12)?'p':'a');
		put_font_char('m');
	}
	else
	{
		glk_window_move_cursor(g.wins[7],(screenwidth/font4width)-30,0);
		glk_put_string("Score: ");
		printNum(score);
		glk_window_move_cursor(g.wins[7],(screenwidth/font4width)-14,0);
		glk_put_string("Moves: ");
		printNum(moves);
	}
	glk_set_window(g.wins[g.curwin]);
}
static void z_sound_effect()
{
}
static void z_split_window()
{
	winid_t p;
	if (g.wins[1])
	{
		p=glk_window_get_parent(g.wins[1]);
		glk_window_set_arrangement(p,winmethod_Above|winmethod_Fixed,args[0],g.wins[1]);
	}
	else
		g.wins[1]=glk_window_open(g.wins[0],winmethod_Above|winmethod_Fixed,args[0],wintype_TextGrid,0);
}
static void z_store()
{
	uint16_t v=args[0];
	if (v==0) poke(SP-4,args[1]);
	else if (v<16) poke(FP+v*4,args[1]);
	else write16(read16(12)+2*(v-16),args[1]);
}
static void z_storeb()
{
	rom[args[0]+args[1]]=args[2]&0xff;
}
static void z_storew()
{
	write16(args[0]+2*args[1],args[2]);
}
static void z_sub()
{
	store((int16_t)args[0]-(int16_t)args[1]);
}
static void z_test()
{
	branch((args[0]&args[1])==args[1]);
}
static void z_test_attr()
{
	uint16_t addr=object_address(args[0]);
	addr+=args[1]/8;
	branch(rom[addr]&(0x80>>(args[1]&7)));
}
static void z_throw()
{
	FP=args[1];
	ret(args[0]);
}
static void z_tokenise()
{
	if (argc<3) args[2]=0;
	if (argc<4) args[3]=0;
	tokenize_line(args[0],args[1],args[2],args[3]!=0);
}
static void z_verify()
{
	// waste of time... let's just say ok
	branch(1);
}
static void z_window_size()
{
	// not a real window.. it's a write mask.. and screw v6 roms
}
static void z_window_style(){fatal("window_style");}
static void z_143()
{
	if (g.version<5) z_not();
	else z_call_n();
}
static void z_185()
{
	if (g.version<5) z_pop();
	else z_catch();
}

static void operand(uint8_t type)
{
	uint16_t value;
	if (type&2)
	{
		uint8_t v=rom[PC++];
		if (v==0) value=pop();
		else if (v<16) value=peek(FP+v*4);
		else value=read16(read16(12)+2*(v-16));
	}
	else if (type&1)
		value=rom[PC++];
	else
	{
		value=read16(PC); PC+=2;
	}
	args[argc++]=value;
}
static void operands(uint8_t types)
{
	uint8_t type;
	int i;
	for (i=6;i>=0;i-=2)
	{
		type=(types>>i)&3;
		if (type==3) break;
		operand(type);
	}
}
static void store(uint16_t value)
{
	uint8_t v=rom[PC++];
	if (v==0) push(value);
	else if (v<16) poke(FP+v*4,value);
	else write16(read16(12)+2*(v-16),value);
}
static void branch(uint16_t flag)
{
	uint8_t type=rom[PC++];
	int16_t offset=type&0x3f;
	if (!flag) type^=0x80;
	if (!(type&0x40))
	{
		offset=(offset<<8)|rom[PC++];
		if (offset&0x2000) offset|=0xc000;
	}
	if (type&0x80)
	{
		if (offset==0 || offset==1) ret(offset);
		else PC+=offset-2;
	}
}
static void init_header()
{
	uint16_t flags=read16(0x10);
	if (g.version<4 && g.wins[7]==0)
		g.wins[7]=glk_window_open(g.wins[0],winmethod_Above|winmethod_Fixed,1,wintype_TextGrid,0);
	//split screen and variable pitch
	if (g.version==3) rom[1]|=0x60;
	//color bold italic monospace timed keyboard available
	if (g.version>3) rom[1]|=0x9d;
	//no sound
	if (g.version==3) flags&=~0x10;
	//no sound, no undo, no mouse, no pictures
	if (g.version>4) flags&=~0xb8;
	write16(0x10,flags);

	if (g.version>3)
	{
		//interpreter number and version, screen width and height
		rom[0x1e]=3; rom[0x1f]=70; rom[0x20]=screenheight/font4height; rom[0x21]=screenwidth/font4width;
	}
	if (g.version>4)
	{
		write16(0x22,screenwidth/font4width); write16(0x24,screenheight/font4height);
		rom[0x26]=1; rom[0x27]=1;
		// default background and foreground
		rom[0x2c]=9; rom[0x2d]=2;
	}
	if (g.numExtensions>3)
	{
		write16(g.extensions+4*2,0); //clear all flags3
		// no really, we're black on white
		if (g.numExtensions>4)
			write16(g.extensions+5*2,0);
		if (g.numExtensions>5)
			write16(g.extensions+6*2,0x7fff);
	}
	// we follow the 1.1 standard.. mostly
	rom[0x32]=1;
	rom[0x33]=1;
}

static void seed_random(int value)
{
	if (value==0)
	{
		g.randa=time(0);
		g.randi=0;
	}
	else if (value<1000)
	{
		g.randc=0;
		g.randi=value;
	}
	else
	{
		g.randa=value;
		g.randi=0;
	}
}
static void call(uint16_t addr,int8_t n,uint16_t *v,uint8_t ct)
{
	uint16_t value;
	uint8_t count,i;

	push(PC>>16);
	push(PC&0xffff);
	push(FP);
	push(n|(ct<<8));
	FP=SP-4;
	if (g.version<4) PC=addr<<1;
	else if (g.version<6) PC=addr<<2;
	else if (g.version<8) PC=(addr<<2)+(read16(40)<<3);
	else PC=addr<<3;

	count=rom[PC++];
	value=0;
	for (i=0;i<count;i++)
	{
		if (g.version<5) { value=read16(PC); PC+=2; }
		push((n-->0)?v[i]:value);
	}
	if (ct==2) z_run();
}
static void ret(uint16_t value)
{
	uint8_t ct;
	SP=FP+4;
	ct=pop()>>8;
	FP=pop();
	PC=pop();
	PC|=pop()<<16;
	if (ct==0) store(value);
	if (ct==2)
	{
		push(value);
		done++;
	}
}
static uint16_t first_prop(uint16_t obj)
{
	uint16_t addr=object_address(obj)+(g.version<4?7:12);
	addr=read16(addr);
	return addr+1+2*rom[addr];
}
static uint16_t next_prop(uint16_t addr)
{
	uint8_t value;
	value=rom[addr++];
	if (g.version<4) value>>=5;
	else if (value&0x80) value=rom[addr]&0x3f;
	else value>>=6;
	return addr+value+1;
}

static uint16_t zsciitable[]={
0xe4,0xf6,0xfc,0xc4,0xd6,0xdc,0xdf,0xbb,0xab,0xeb,0xef,0xff,0xcb,0xcf,
0xe1,0xe9,0xed,0xf3,0xfa,0xfd,0xc1,0xc9,0xcd,0xd3,0xda,0xdd,0xe0,0xe8,
0xec,0xf2,0xf9,0xc0,0xc8,0xcc,0xd2,0xd9,0xe2,0xea,0xee,0xf4,0xfb,0xc2,
0xca,0xce,0xd4,0xdb,0xe5,0xc5,0xf8,0xd8,0xe3,0xf1,0xf5,0xc3,0xd1,0xd5,
0xe6,0xc6,0xe7,0xc7,0xfe,0xf0,0xde,0xd0,0xa3,0x153,0x152,0xa1,0xbf};


static uint16_t zscii2ascii(uint8_t c)
{
	if (c>=0x9b)
	{
		if (g.unicode)
		{
			uint8_t numuni;
			numuni=rom[g.unicode];
			if (c-0x9b<numuni)
				return read16(g.unicode+1+2*(c-0x9b));
			return '?';
		}
		else if (c<0xe0) return zsciitable[c-0x9b];
		return '?';
	}
	return c;
}
static uint8_t ascii2zscii(uint8_t c)
{
	uint8_t i;
	if (c>=0xa0)
	{
		if (g.unicode)
		{
			uint8_t numuni=rom[g.unicode];
			for (i=0;i<numuni;i++)
				if (c==read16(g.unicode+1+2*i)) return 0x9b+i;
			return '?';
		}
		else
		{
			for (i=0x9b;i<=0xdf;i++)
				if (c==zsciitable[i-0x9b]) return i;
			return '?';
		}
	}
	if (c==0) c='?';
	return c;
}
uint16_t alphabet(uint8_t set,uint8_t index)
{
	uint16_t alpha=read16(52);
	if (alpha!=0)
		return zscii2ascii(rom[alpha+26*set+index]);
	if (set==0) return 'a'+index;
	if (set==1) return 'A'+index;
	if (g.version==1) return " 0123456789.,!?_#'\"/\\<-:()"[index];
	return " ^0123456789.,!?_#'\"/\\-:()"[index];
}
static void decode(uint8_t st,uint32_t addr)
{
	uint16_t code;
	uint8_t prev,c,status=0,shift=0,lock=00;
	int i;

	if (st==1) addr<<=1;
	if (st==2)
	{
		if (g.version<4) addr<<=1;
		else if (g.version<6) addr<<=2;
		else if (g.version<8) addr=(addr<<2)+(read16(42)<<3);
		else addr<<=3;
	}
	do
	{
		if (st<3)
		{
			code=read16(addr); addr+=2;
		}
		else
		{
			code=read16(PC); PC+=2;
		}
		for (i=10;i>=0;i-=5)
		{
			c=(code>>i)&0x1f;
			switch (status)
			{
				case 0:
					if (shift==2 && c==6) status=2;
					else if (g.version==1 && c==1) put_font_char('\n');
					else if (g.version>1 && shift==2 && c==7) put_font_char('\n');
					else if (c>=6) put_font_char(alphabet(shift,c-6));
					else if (c==0) put_font_char(' ');
					else if (g.version>1 && c==1) status=1;
					else if (g.version>2 && c<4) status=1;
					else {
						shift=(lock+(c&1)+1)%3;
						if (g.version<3 && c>5) lock=shift;
						break;
					}
					shift=lock;
					break;
				case 1:
					decode(1,read16(read16(24)+64*(prev-1)+2*c));
					status=0;
					break;
				case 2:
					status=3;
					break;
				case 3:
					put_font_char(zscii2ascii(c|(prev<<5)));
					status=0;
					break;
			}
			prev=c;
		}
	} while (!(code&0x8000));
}
static void unlink_object(uint16_t obj)
{
	uint16_t addr=object_address(obj);

	if (g.version<4)
	{
		uint8_t prev,next,parent=rom[addr+4];
		if (!parent) return;
		rom[addr+4]=0;
		next=rom[addr+5];
		rom[addr+5]=0;
		addr=object_address(parent);
		prev=rom[addr+6];
		if (prev==obj) rom[addr+6]=next;
		else
		{
			do {
				addr=object_address(prev);
				prev=rom[addr+5];
			} while (prev!=obj);
			rom[addr+5]=next;
		}
	}
	else
	{
		uint16_t prev,next,parent=read16(addr+6);
		if (!parent) return;
		write16(addr+6,0);
		next=read16(addr+8);
		write16(addr+8,0);
		addr=object_address(parent);
		prev=read16(addr+10);
		if (prev==obj)
			write16(addr+10,next);
		else
		{
			do {
				addr=object_address(prev);
				prev=read16(addr+8);
			} while (prev!=obj);
			write16(addr+8,next);
		}
	}
}
static void load_string(uint16_t addr,uint8_t len)
{
	uint8_t res=g.version<4?6:9,i=00;
	while (i<res)
		if (i<len) decoded[i++]=zscii2ascii(rom[addr++]);
		else decoded[i++]=0;
}
static void encode_text(uint8_t pad)
{
	uint8_t i=0,res=(g.version<4)?2:3;
	uint8_t c,ptr=0;
	uint8_t zchars[12];

	while (i<res*3)
		if ((c=decoded[ptr++])!=0)
		{
			uint8_t set,index,found=00;
			for (set=0;set<3;set++)
				for (index=0;index<26;index++)
					if (c==alphabet(set,index))
					{
						found=1;
						if (set) zchars[i++]=(g.version<3?1:3)+set;
						zchars[i++]=index+6;
						break;
					}
			if (!found)
			{
				c=ascii2zscii(c);
				zchars[i++]=5;
				zchars[i++]=6;
				zchars[i++]=c>>5;
				zchars[i++]=c&0x1f;
			}
		}
		else zchars[i++]=pad;
	for (i=0;i<res;i++)
		encoded[i]=(zchars[3*i]<<10)|(zchars[3*i+1]<<5)|zchars[3*i+2];
	encoded[res-1]|=0x8000;
}
static uint16_t lookup_text(uint8_t pad,uint16_t dct)
{
	uint8_t sepCount,entryLen,sorted=1;
	uint8_t res=(g.version<4)?2:3,i;
	int16_t entryCount,lower,upper,entryNumber;
	uint16_t entryAddr,addr,entry;

	encode_text(pad);
	sepCount=rom[dct];
	dct+=1+sepCount;
	entryLen=rom[dct++];
	entryCount=read16(dct); dct+=2;
	if (entryCount<0)
	{
		entryCount=-entryCount;
		sorted=0;
	}
	lower=0;
	upper=entryCount-1;
	while (lower<=upper)
	{
		uint8_t found=1;
		entryNumber=sorted?((lower+upper)>>1):lower;
		addr=entryAddr=dct+entryNumber*entryLen;
		for (i=0;i<res;i++)
		{
			entry=read16(addr);
			if (encoded[i]!=entry) { found=0; break; }
			addr+=2;
		}
		if (found) return entryAddr;
		if (sorted)
			if (encoded[i]>entry) lower=entryNumber+1;
			else upper=entryNumber-1;
		else lower++;
	}
	if (pad==0x5) return 0;
	entryNumber=(pad==0x0)?lower:upper;
	if (entryNumber==-1 || entryNumber==entryCount) return 0;
	return dct+entryNumber*entryLen;
}
static void tokenize_text(uint16_t text,uint8_t len,uint8_t from,uint16_t parse,uint16_t dct,uint8_t flag)
{
	uint16_t addr;
	uint8_t max=rom[parse++];
	uint8_t count=rom[parse];

	if (count<max)
	{
		rom[parse++]=count+1;
		load_string(text+from,len);
		addr=lookup_text(0x05,dct);
		if (addr || !flag)
		{
			parse+=4*count;
			write16(parse,addr);
			rom[parse+2]=len;
			rom[parse+3]=from;
		}
	}
}
static void tokenize_line(uint16_t text,uint16_t token,uint16_t dct,uint8_t flag)
{
	uint8_t c,len=0;
	uint16_t addr=text,addr2=00;

	if (dct==0) dct=read16(8);
	rom[token+1]=0;
	if (g.version>4) len=rom[++addr];
	do {
		uint16_t sepAddr;
		uint8_t sepCount,sep;
		addr++;
		if (g.version>4 && addr==text+2+len) c=0;
		else c=rom[addr];
		sepAddr=dct;
		sepCount=rom[sepAddr++];
		do {
			sep=rom[sepAddr++];
		} while (c!=sep && --sepCount);
		if (!sepCount && c!=' ' && c!=0)
		{
			if (addr2==0) addr2=addr;
		}
		else if (addr2!=0)
		{
			tokenize_text(text,addr-addr2,addr2-text,token,dct,flag);
			addr2=0;
		}
		if (sepCount)
			tokenize_text(text,1,addr-text,token,dct,flag);
	} while (c!=0);
}
static uint16_t font3[]={
0x0020,0x2190,0x2192,0x0338,0x005c,0x0020,0x2500,0x2500,	//20
0x2502,0x2502,0x2534,0x252c,0x251c,0x2524,0x2514,0x250c,	//28
0x2510,0x2518,0x2514,0x250c,0x2510,0x2518,0x2588,0x2550,	//30
0x2550,0x2551,0x2551,0x2567,0x2564,0x255f,0x2562,0x255a,	//38
0x2554,0x2557,0x255d,0x255a,0x2554,0x2557,0x255d,0x0020,	//40
0x0020,0x0020,0x0020,0x0305,0x0332,0x2502,0x2502,0x25a1,	//48
0x2591,0x2592,0x2592,0x2592,0x2593,0x2593,0x2593,0x25a0,	//50
0x2502,0x2502,0x0058,0x253c,0x2191,0x2193,0x2195,0x25a1,	//58
0x003f,0x03b1,0x03b2,0x03b3,0x03b4,0x03b5,0x03b6,0x03b7,	//60
0x03b8,0x03b9,0x03ba,0x03bb,0x03bc,0x03bd,0x03be,0x03bf,	//68
0x03c0,0x03c1,0x03c2,0x03c3,0x03c4,0x03c5,0x03c6,0x03c7,	//70
0x03c8,0x03c9,0x03d9,0x2191,0x2193,0x2195,0x003f};		//78
static void put_font_char(uint16_t c)
{
	if (g.curwin==1)
	{
		g.cursorx++;
		if (c=='\n')
		{
			g.cursorx=0;
			g.cursory++;
		}
	}
	if (curFont==3 && c>=0x20 && c<0x7f) glk_put_char_uni(font3[c-0x20]);
	else glk_put_char_uni(c);
}
